
#define _GNU_SOURCE
#include "gm_crypto.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/err.h>
// #include <openssl/sm2.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>

static const EVP_MD *g_md = NULL; /* SM3 if available, else SHA256 */

int gm_crypto_init(void) {
    /* 初始化 OpenSSL 算法表（1.1.0 及以上大多数为自动） */
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
#endif
    /* 试图使用 SM3 */
    g_md = EVP_get_digestbyname("sm3");
    if (!g_md) {
        fprintf(stderr, "SM3 digest not supported in this OpenSSL build\n");
        return -1;
    }
    return 0;
}

void gm_crypto_cleanup(void) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    EVP_cleanup();
    ERR_free_strings();
#endif
}

int gm_sm2_sign(EVP_PKEY *pkey, const unsigned char *msg, size_t msglen,
                const char *sm2_id, unsigned char **sig, size_t *siglen)
{
    int ret = -1;
    EVP_MD_CTX *mdctx = NULL;
    EVP_PKEY_CTX *pctx = NULL;

    if (!pkey || !msg || !sig || !siglen) return -1;
    if (!sm2_id || strlen(sm2_id) != 16) return -1;
    *sig = NULL; *siglen = 0;

#ifdef EVP_PKEY_SM2
    //(void)EVP_PKEY_set_alias_type(pkey, EVP_PKEY_SM2);
#endif

    mdctx = EVP_MD_CTX_new();
    if (!mdctx) goto end;

    if (EVP_DigestSignInit(mdctx, &pctx, EVP_sm3(), NULL, pkey) != 1) goto end;

    /* 1) 设 SM2 签名 scheme（部分实现必须） */
#ifdef EVP_PKEY_CTX_set_ec_scheme
    if (EVP_PKEY_CTX_set_ec_scheme(pctx, NID_sm_scheme) <= 0) { /* 忽略返回也可 */
        /* 某些库没有这个控件，继续走 ctrl_str 兜底 */
    }
#endif
    (void)EVP_PKEY_CTX_ctrl_str(pctx, "ec_scheme", "sm2");

    /* 2) 设 digest=sm3（有的库需要显式指定） */
#ifdef EVP_PKEY_CTX_set_signature_md
    (void)EVP_PKEY_CTX_set_signature_md(pctx, EVP_sm3());
#endif
    (void)EVP_PKEY_CTX_ctrl_str(pctx, "digest", "sm3");

    /* 3) 设 SM2_ID（两种方式都设，保证兼容） */
#if defined(EVP_PKEY_CTX_set1_id)
    if (EVP_PKEY_CTX_set1_id(pctx, sm2_id, (int)strlen(sm2_id)) <= 0) goto end;
#else
    if (EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_TYPE_SIG,
                          EVP_PKEY_CTRL_SET1_ID,
                          (int)strlen(sm2_id), (void*)sm2_id) <= 0) goto end;
#endif
    (void)EVP_PKEY_CTX_ctrl_str(pctx, "sm2_id", sm2_id);

    if (EVP_DigestSignUpdate(mdctx, msg, msglen) != 1) goto end;

    if (EVP_DigestSignFinal(mdctx, NULL, siglen) != 1) goto end;
    *sig = OPENSSL_malloc(*siglen);
    if (!*sig) goto end;
    if (EVP_DigestSignFinal(mdctx, *sig, siglen) != 1) goto end;

    ret = 0;

end:
    if (ret != 0) {
        if (*sig) { OPENSSL_free(*sig); *sig = NULL; *siglen = 0; }
        ERR_print_errors_fp(stderr);  /* 打印 OpenSSL 错误，便于定位 */
    }
    if (mdctx) EVP_MD_CTX_free(mdctx);
    return ret;
}

int gm_sm2_verify(EVP_PKEY *pkey, const unsigned char *msg, size_t msglen,
                  const char *sm2_id, const unsigned char *sig, size_t siglen)
{
    int ret = -1;
    EVP_MD_CTX *mdctx = NULL;
    EVP_PKEY_CTX *pctx = NULL;

    if (!pkey || !msg || !sig) return -1;
    if (!sm2_id || strlen(sm2_id) != 16) return -1;

#ifdef EVP_PKEY_SM2
    //(void)EVP_PKEY_set_alias_type(pkey, EVP_PKEY_SM2);
#endif

    mdctx = EVP_MD_CTX_new();
    if (!mdctx) goto end;

    if (EVP_DigestVerifyInit(mdctx, &pctx, EVP_sm3(), NULL, pkey) != 1) goto end;

#ifdef EVP_PKEY_CTX_set_ec_scheme
    if (EVP_PKEY_CTX_set_ec_scheme(pctx, NID_sm_scheme) <= 0) {
        /* 继续用 ctrl_str 兜底 */
    }
#endif
    (void)EVP_PKEY_CTX_ctrl_str(pctx, "ec_scheme", "sm2");

#ifdef EVP_PKEY_CTX_set_signature_md
    (void)EVP_PKEY_CTX_set_signature_md(pctx, EVP_sm3());
#endif
    (void)EVP_PKEY_CTX_ctrl_str(pctx, "digest", "sm3");

#if defined(EVP_PKEY_CTX_set1_id)
    if (EVP_PKEY_CTX_set1_id(pctx, sm2_id, (int)strlen(sm2_id)) <= 0) goto end;
#else
    if (EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_TYPE_SIG,
                          EVP_PKEY_CTRL_SET1_ID,
                          (int)strlen(sm2_id), (void*)sm2_id) <= 0) goto end;
#endif
    (void)EVP_PKEY_CTX_ctrl_str(pctx, "sm2_id", sm2_id);

    if (EVP_DigestVerifyUpdate(mdctx, msg, msglen) != 1) goto end;

    {
        int v = EVP_DigestVerifyFinal(mdctx, sig, siglen);
        if (v == 1) ret = 1;
        else if (v == 0) ret = 0;
        else ret = -1;
    }

end:
    if (ret != 1) ERR_print_errors_fp(stderr);  /* 失败时输出堆栈 */
    if (mdctx) EVP_MD_CTX_free(mdctx);
    return ret;
}

/* base64 encode */
char *gm_sig_base64(const unsigned char *sig, size_t sig_len) {
    if (!sig || sig_len == 0) return NULL;
    BIO *b64 = BIO_new(BIO_f_base64());
    BIO *bmem = BIO_new(BIO_s_mem());
    if (!b64 || !bmem) { if (b64) BIO_free(b64); if (bmem) BIO_free(bmem); return NULL; }

    /* 去掉换行 */
#ifdef BIO_FLAGS_BASE64_NO_NL
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
#endif

    BIO_push(b64, bmem);
    if (BIO_write(b64, sig, sig_len) <= 0) { BIO_free_all(b64); return NULL; }
    (void)BIO_flush(b64);

    BUF_MEM *bptr = NULL;
    BIO_get_mem_ptr(b64, &bptr);
    if (!bptr) { BIO_free_all(b64); return NULL; }

    char *out = malloc(bptr->length + 1);
    if (!out) { BIO_free_all(b64); return NULL; }
    memcpy(out, bptr->data, bptr->length);
    out[bptr->length] = '\0';

    BIO_free_all(b64);
    return out;
}

/* base64 decode */
unsigned char *gm_base64_decode(const char *b64, size_t *out_len) {
    if (!b64 || !out_len) return NULL;
    BIO *b64bio = BIO_new(BIO_f_base64());
    BIO *bmem = BIO_new_mem_buf(b64, -1);
    if (!b64bio || !bmem) { if (b64bio) BIO_free(b64bio); if (bmem) BIO_free(bmem); return NULL; }

#ifdef BIO_FLAGS_BASE64_NO_NL
    BIO_set_flags(b64bio, BIO_FLAGS_BASE64_NO_NL);
#endif

    BIO_push(b64bio, bmem);

    /* 预分配一个缓冲区（签名通常较短，2048 bytes 足够，若不够会调整） */
    size_t bufsize = strlen(b64) * 3 / 4 + 16;
    unsigned char *buf = malloc(bufsize);
    if (!buf) { BIO_free_all(b64bio); return NULL; }

    int len = BIO_read(b64bio, buf, (int)bufsize);
    if (len <= 0) { free(buf); BIO_free_all(b64bio); return NULL; }

    *out_len = (size_t)len;
    BIO_free_all(b64bio);
    return buf;
}
